sysroot: Cache deployment device/inode
authorColin Walters <walters@verbum.org>
Tue, 1 Jul 2025 14:54:52 +0000 (10:54 -0400)
committerColin Walters <walters@verbum.org>
Tue, 1 Jul 2025 17:15:34 +0000 (13:15 -0400)
This is a lot cleaner. In particular, it's prep
for having soft reboot finalize at shutdown time, because
we weren't correctly handling the staged deployment in
that case.

To implement this we need to basically stop using
ostree_deployment_new() in most places, and instead ensure that all
callers use this path which cache the device/inode.

Signed-off-by: Colin Walters <walters@verbum.org>
src/libostree/ostree-deployment-private.h
src/libostree/ostree-deployment.c
src/libostree/ostree-sysroot-cleanup.c
src/libostree/ostree-sysroot-deploy.c
src/libostree/ostree-sysroot-private.h
src/libostree/ostree-sysroot.c

index 55a3bded0f3bb3e111db3d16b33d8570699a2996..367f68eaf4b8c98ba10f48cbfb5b19ded5c6d064 100644 (file)
@@ -55,6 +55,11 @@ struct _OstreeDeployment
   gboolean soft_reboot_target;
   char **overlay_initrds;
   char *overlay_initrds_id;
+
+  // Private cache of expected backing device/inode
+  gboolean devino_initialized;
+  dev_t device;
+  ino_t inode;
 };
 
 void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum);
index b4427262cfbf4d5de4b7cb04bf78b024fd0fd52a..a11efe558c702f0d61be9a40098377ebeada1da5 100644 (file)
@@ -255,6 +255,10 @@ ostree_deployment_clone (OstreeDeployment *self)
   OstreeDeployment *ret = ostree_deployment_new (
       self->index, self->osname, self->csum, self->deployserial, self->bootcsum, self->bootserial);
 
+  ret->devino_initialized = TRUE;
+  ret->device = self->device;
+  ret->inode = self->inode;
+
   new_bootconfig = ostree_bootconfig_parser_clone (self->bootconfig);
   ostree_deployment_set_bootconfig (ret, new_bootconfig);
 
index 5d44af4596c42d382aece8d5b647ade41cfb4422..6d2accde81103a05f7a7a280935f6fc7af25015e 100644 (file)
@@ -32,8 +32,8 @@
  * @inout_deployments: All deployments in this subdir will be appended to this array
  */
 gboolean
-_ostree_sysroot_list_deployment_dirs_for_os (int deploydir_dfd, const char *osname,
-                                             GPtrArray *inout_deployments,
+_ostree_sysroot_list_deployment_dirs_for_os (OstreeSysroot *self, int deploydir_dfd,
+                                             const char *osname, GPtrArray *inout_deployments,
                                              GCancellable *cancellable, GError **error)
 {
   g_auto (GLnxDirFdIterator) dfd_iter = {
@@ -63,8 +63,11 @@ _ostree_sysroot_list_deployment_dirs_for_os (int deploydir_dfd, const char *osna
       if (!_ostree_sysroot_parse_deploy_path_name (dent->d_name, &csum, &deployserial, error))
         return FALSE;
 
-      g_ptr_array_add (inout_deployments,
-                       ostree_deployment_new (-1, osname, csum, deployserial, NULL, -1));
+      OstreeDeployment *deploy = _ostree_sysroot_new_deployment_object (
+          self, osname, csum, deployserial, NULL, -1, error);
+      if (!deploy)
+        return FALSE;
+      g_ptr_array_add (inout_deployments, deploy);
     }
 
   return TRUE;
@@ -100,8 +103,8 @@ list_all_deployment_directories (OstreeSysroot *self, GPtrArray **out_deployment
       if (dent->d_type != DT_DIR)
         continue;
 
-      if (!_ostree_sysroot_list_deployment_dirs_for_os (dfd_iter.fd, dent->d_name, ret_deployments,
-                                                        cancellable, error))
+      if (!_ostree_sysroot_list_deployment_dirs_for_os (self, dfd_iter.fd, dent->d_name,
+                                                        ret_deployments, cancellable, error))
         return FALSE;
     }
 
index 2d676be15aad27d0a31c28ca19981cacdad746bf..2a67432650be096b582ba7e0c3d72e83765fd5c5 100644 (file)
@@ -3033,8 +3033,8 @@ allocate_deployserial (OstreeSysroot *self, const char *osname, const char *revi
   if (!glnx_opendirat (self->sysroot_fd, "ostree/deploy", TRUE, &deploy_dfd, error))
     return FALSE;
 
-  if (!_ostree_sysroot_list_deployment_dirs_for_os (deploy_dfd, osname, tmp_current_deployments,
-                                                    cancellable, error))
+  if (!_ostree_sysroot_list_deployment_dirs_for_os (self, deploy_dfd, osname,
+                                                    tmp_current_deployments, cancellable, error))
     return FALSE;
 
   for (guint i = 0; i < tmp_current_deployments->len; i++)
@@ -3206,6 +3206,13 @@ sysroot_initialize_deployment (OstreeSysroot *self, const char *osname, const ch
                                  &checkout_elapsed, &composefs_elapsed, cancellable, error))
     return FALSE;
 
+  struct stat stbuf;
+  if (fstat (deployment_dfd, &stbuf) < 0)
+    return glnx_throw_errno_prefix (error, "fstat(deployment fd)");
+  new_deployment->devino_initialized = TRUE;
+  new_deployment->device = stbuf.st_dev;
+  new_deployment->inode = stbuf.st_ino;
+
   g_autoptr (OstreeKernelLayout) kernel_layout = NULL;
   if (!get_kernel_from_tree (self, deployment_dfd, &kernel_layout, cancellable, error))
     return FALSE;
@@ -3651,7 +3658,8 @@ require_str_key (GVariantDict *dict, const char *name, const char **ret, GError
  * higher level code.
  */
 OstreeDeployment *
-_ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, GError **error)
+_ostree_sysroot_deserialize_deployment_from_variant (OstreeSysroot *self, GVariant *v,
+                                                     GError **error)
 {
   g_autoptr (GVariantDict) dict = g_variant_dict_new (v);
   const char *name = NULL;
@@ -3667,7 +3675,8 @@ _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, GError **error
   gint deployserial;
   if (!_ostree_sysroot_parse_deploy_path_name (name, &checksum, &deployserial, error))
     return NULL;
-  return ostree_deployment_new (-1, osname, checksum, deployserial, bootcsum, -1);
+  return _ostree_sysroot_new_deployment_object (self, osname, checksum, deployserial, bootcsum, -1,
+                                                error);
 }
 
 /**
@@ -4000,7 +4009,7 @@ _ostree_sysroot_finalize_staged_inner (OstreeSysroot *self, GCancellable *cancel
                         &merge_deployment_v))
     {
       g_autoptr (OstreeDeployment) merge_deployment_stub
-          = _ostree_sysroot_deserialize_deployment_from_variant (merge_deployment_v, error);
+          = _ostree_sysroot_deserialize_deployment_from_variant (self, merge_deployment_v, error);
       if (!merge_deployment_stub)
         return FALSE;
       for (guint i = 0; i < self->deployments->len; i++)
index 6370592f33ccfb5f72762d9c3898a34be6be0869..1ca12c268b427431fab02d4e5fc4ac5cec15a1ab 100644 (file)
@@ -128,6 +128,12 @@ struct OstreeSysroot
 
 gboolean _ostree_sysroot_ensure_writable (OstreeSysroot *self, GError **error);
 
+// Should be preferred over ostree_deployment_new
+OstreeDeployment *_ostree_sysroot_new_deployment_object (OstreeSysroot *self, const char *osname,
+                                                         const char *csum, int deployserial,
+                                                         const char *bootcsum, int bootserial,
+                                                         GError **error);
+
 void _ostree_sysroot_emit_journal_msg (OstreeSysroot *self, const char *msg);
 
 gboolean _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
@@ -141,7 +147,8 @@ gboolean _ostree_sysroot_read_current_subbootversion (OstreeSysroot *self, int b
 gboolean _ostree_sysroot_parse_deploy_path_name (const char *name, char **out_csum, int *out_serial,
                                                  GError **error);
 
-gboolean _ostree_sysroot_list_deployment_dirs_for_os (int deploydir_dfd, const char *osname,
+gboolean _ostree_sysroot_list_deployment_dirs_for_os (OstreeSysroot *self, int deploydir_dfd,
+                                                      const char *osname,
                                                       GPtrArray *inout_deployments,
                                                       GCancellable *cancellable, GError **error);
 
@@ -157,7 +164,8 @@ gboolean _ostree_sysroot_boot_complete (OstreeSysroot *self, GCancellable *cance
 
 gboolean _ostree_prepare_soft_reboot (GError **error);
 
-OstreeDeployment *_ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, GError **error);
+OstreeDeployment *_ostree_sysroot_deserialize_deployment_from_variant (OstreeSysroot *self,
+                                                                       GVariant *v, GError **error);
 
 char *_ostree_sysroot_get_deployment_backing_relpath (OstreeDeployment *deployment);
 
index f9955ce23375db3d3bd4507ddfd4a1cfa3648c07..e861b716ad55fb2c57223c35e9592443ae653517 100644 (file)
@@ -826,6 +826,31 @@ _ostree_sysroot_get_runstate_path (OstreeDeployment *deployment, const char *key
                           ostree_deployment_get_deployserial (deployment), key);
 }
 
+// Should be preferred over ostree_deployment_new as this also initializes cached
+// state for device/inode.
+OstreeDeployment *
+_ostree_sysroot_new_deployment_object (OstreeSysroot *self, const char *osname, const char *csum,
+                                       int deployserial, const char *bootcsum, int bootserial,
+                                       GError **error)
+{
+  if (!ensure_sysroot_fd (self, error))
+    return FALSE;
+
+  // This deployment isn't associated with an index.
+  g_autoptr (OstreeDeployment) ret
+      = ostree_deployment_new (-1, osname, csum, deployserial, bootcsum, bootserial);
+
+  g_autofree char *relpath = ostree_sysroot_get_deployment_dirpath (self, ret);
+  struct stat stbuf;
+  if (!glnx_fstatat (self->sysroot_fd, relpath, &stbuf, AT_SYMLINK_NOFOLLOW, error))
+    return NULL;
+  ret->devino_initialized = TRUE;
+  ret->device = stbuf.st_dev;
+  ret->inode = stbuf.st_ino;
+
+  return g_steal_pointer (&ret);
+}
+
 static gboolean
 parse_deployment (OstreeSysroot *self, const char *boot_link, OstreeDeployment **out_deployment,
                   GCancellable *cancellable, GError **error)
@@ -904,9 +929,6 @@ parse_deployment (OstreeSysroot *self, const char *boot_link, OstreeDeployment *
       is_booted_deployment
           = stbuf.st_dev == expected_root_dev && stbuf.st_ino == expected_root_inode;
     }
-  gboolean is_soft_reboot_target
-      = self->have_nextroot
-        && (stbuf.st_dev == self->nextroot_device && stbuf.st_ino == self->nextroot_inode);
 
   g_autoptr (OstreeDeployment) ret_deployment
       = ostree_deployment_new (-1, osname, treecsum, deployserial, bootcsum, treebootserial);
@@ -934,7 +956,10 @@ parse_deployment (OstreeSysroot *self, const char *boot_link, OstreeDeployment *
         }
       /* TODO: warn on unknown unlock types? */
     }
-  ret_deployment->soft_reboot_target = is_soft_reboot_target;
+
+  ret_deployment->devino_initialized = TRUE;
+  ret_deployment->device = stbuf.st_dev;
+  ret_deployment->inode = stbuf.st_ino;
 
   g_debug ("Deployment %s.%d unlocked=%d", treecsum, deployserial, ret_deployment->unlocked);
 
@@ -1179,7 +1204,7 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error)
       if (target)
         {
           g_autoptr (OstreeDeployment) staged
-              = _ostree_sysroot_deserialize_deployment_from_variant (target, error);
+              = _ostree_sysroot_deserialize_deployment_from_variant (self, target, error);
           if (!staged)
             return FALSE;
 
@@ -1312,11 +1337,18 @@ sysroot_load_from_bootloader_configs (OstreeSysroot *self, GCancellable *cancell
   if (self->staged_deployment)
     g_ptr_array_insert (deployments, 0, g_object_ref (self->staged_deployment));
 
-  /* And then set their index variables */
+  /* Synchronize internal state now that we've loaded all deployments */
   for (guint i = 0; i < deployments->len; i++)
     {
       OstreeDeployment *deployment = deployments->pdata[i];
       ostree_deployment_set_index (deployment, i);
+
+      g_assert (deployment->devino_initialized);
+      if (self->have_nextroot && deployment->device == self->nextroot_device
+          && deployment->inode == self->nextroot_inode)
+        {
+          deployment->soft_reboot_target = TRUE;
+        }
     }
 
   /* Determine whether we're "physical" or not, the first time we load deployments */